package com.avast.personalbudgetapp.model.service;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;

import android.util.Log;

import com.avast.personalbudgetapp.model.bean.Proto;
import com.avast.personalbudgetapp.model.bean.Proto.AccountDelta;
import com.avast.personalbudgetapp.model.bean.Proto.Transaction;

/**
 * BudgetApp Service Implementation - Singleton
 * 
 * @author hallac
 *
 */
public class BudgetAppServiceImpl implements BudgetAppService {
	
	private static BudgetAppServiceImpl _instance;
	
	private Proto.AccountDelta.Builder accountDeltaBuilder = AccountDelta.newBuilder();
	private AccountDelta accountDelta;
	private Long timestampOfLastSuccessfulSync;
	
	private static String TAG = BudgetAppServiceImpl.class.getName();
	
	//TODO Make it configurable
	private static String URL = "http://bean-keeper.appspot.com/bk";
	
	public static BudgetAppServiceImpl getInstance(){
		if(_instance==null){
			_instance = new BudgetAppServiceImpl();
		}
		return _instance;		
	}

	private BudgetAppServiceImpl(){
		if(accountDelta == null){
			accountDelta = accountDeltaBuilder.build();	
		} else {
			accountDeltaBuilder.mergeFrom(accountDelta);
		}
	}
	
	/**
	 * Insert & update transaction
	 */
	@Override
	public void insertOrUpdate(Transaction transaction) {
		if(!transaction.getDeleted()){
			accountDeltaBuilder.addAddedOrModified(transaction);
		}
	}

	//TODO Mark transaction as deleted, do not remove!
	/**
	 * Remove transaction
	 */
	@Override
	public void removeTransaction(String guid) {
		
		int size = accountDeltaBuilder.getAddedOrModifiedList().size();
		for(int i=0; i<size; i++){
			Transaction transaction = accountDeltaBuilder.getAddedOrModifiedList().get(i);
			if(transaction.getGuid().equals(guid)){
				accountDeltaBuilder.removeAddedOrModified(i);
				break;
			}
		}
		
	}

	/**
	 * Get new or modified transactions
	 */
	@Override
	public List<Transaction> getDirtyTransactions() {
		
		List<Transaction> transactions = new ArrayList<Transaction>();
		//filtering non-deleted transactions
		for (Transaction transaction : accountDeltaBuilder.getAddedOrModifiedList()) {
			if(!transaction.getDeleted()){
				transactions.add(transaction);
			}
		}
		
		return transactions;
		
	}

	/**
	 * Get the sum of transactions
	 * 
	 */
	@Override
	public Double getBalance() {

		Double sum = (double) 0;
		if(accountDelta != null){
			List<Transaction> transactions = accountDeltaBuilder.getAddedOrModifiedList();
			for (Transaction transaction : transactions) {
				if(!transaction.getDeleted()){
					sum = sum + transaction.getValue();
				}
			}
		}
		
		return sum;
	}

	/**
	 * Get timestamp of last successful synchronization
	 */
	@Override
	public Long getTimestampOfLastSuccessfulSync() {
		return timestampOfLastSuccessfulSync;
	}

	/**
	 * Set timestamp of last successful synchronization
	 */
	@Override
	public void setTimestampOfLastSuccessfulSync(Long timestamp) {
		this.timestampOfLastSuccessfulSync = timestamp;
	}

	/**
	 * Send the account to backend server
	 */
	@Override
	public AccountDelta sendAccountDeltaToServer(AccountDelta accountDelta) throws ClientProtocolException, IOException {

		HttpClient client = new DefaultHttpClient();
		HttpPost post = new HttpPost(URL);
		post.setEntity(new ByteArrayEntity(accountDeltaBuilder.mergeFrom(accountDelta).build().toByteArray()));
		HttpResponse response = client.execute(post);
		InputStream inputStream = response.getEntity().getContent();	
		return AccountDelta.parseFrom(inputStream);
		
	}

	/**
	 * Mark all transactions as clean
	 */
	//TODO Fix duplication issue
	@Override
	public void markAllTransactionsClean() {
		
		int index = 0;
		List<Transaction> transactions = accountDeltaBuilder.getAddedOrModifiedList();
		for (Transaction transaction : transactions) {
			
			Transaction.Builder transactionBuilder = Transaction.newBuilder();			
			Transaction.Builder transactionBuilderToClean = transactionBuilder.mergeFrom(transaction);
			transactionBuilderToClean.setDeleted(true);
			
			accountDeltaBuilder.setAddedOrModified(index, transactionBuilderToClean.build());
			
			index++;
		}
		
		accountDelta = accountDeltaBuilder.build();
		
	}

	/**
	 * Sync data with server
	 */
	//TODO Fix this method by using markAllTransactionsClean() in proper way. At the moment, it duplicates all transactions.
	@Override
	public void syncData() {
		
		List<Transaction> transactions = getDirtyTransactions(); // Dirty means new or modified
		//TODO What for?: clientTimestamp may be used either for logging purposes or periodical sync. 
		//E.g.: If the last successful sync was 5 min ago, do not sync again
		Long clientTimestamp = getTimestampOfLastSuccessfulSync();
		if(clientTimestamp != null){
			Log.i(TAG, "Last successful sync: " + new Date(clientTimestamp));	
		}

		Proto.AccountDelta.Builder outgoingAccountDeltaBuilder = AccountDelta.newBuilder();
		for (Transaction transaction : transactions) {
			outgoingAccountDeltaBuilder.addAddedOrModified(transaction);
		}

		AccountDelta incomingAccountDelta = null;
		try {
			incomingAccountDelta = sendAccountDeltaToServer(outgoingAccountDeltaBuilder.build());
		} catch (ClientProtocolException e) {
			Log.e(TAG, e.getLocalizedMessage(), e);
		} catch (IOException e) {
			Log.e(TAG, e.getLocalizedMessage(), e);
		}

		if (incomingAccountDelta != null) {
			
		  // The sync was successful
//		  markAllTransactionsClean(); // Clean means not new and not modified
		  for (Transaction incomingTransaction : incomingAccountDelta.getAddedOrModifiedList()) {
			  // Add transactions added or modified by other clients
			  insertOrUpdate(incomingTransaction);
		  }
		  
		  setTimestampOfLastSuccessfulSync(incomingAccountDelta.getServerTimestamp());	
		  
		}
		
	}

}
